#pragma warning(disable : 4355)
#include "Avalon/Threading/LiveTimer.hpp"
#include <boost/bind.hpp>
#pragma warning(disable : 4267)
#include <boost/thread.hpp>
#pragma warning(default : 4267)
#ifdef Yield
  #undef Yield
#endif
#include "Avalon/Base/Singleton.hpp"

using namespace Avalon;
using namespace Avalon::Threading;
using namespace boost;
using namespace boost::asio;
using namespace boost::posix_time;
using namespace std;

namespace {
  class TimerService : private Singleton<TimerService> {
    public:

      TimerService()
          : m_work(new io_service::work(m_service)),
            m_serviceThread(new thread(bind(&io_service::run,
              ref(m_service)))) {}

      virtual ~TimerService() {
        m_work.reset();
        m_service.stop();
        m_serviceThread->join();
      }

      io_service& GetService() {
        return m_service;
      }

      using Singleton<TimerService>::GetInstance;

    private:
      friend class Singleton<TimerService>;
      io_service m_service;
      scoped_ptr<io_service::work> m_work;
      scoped_ptr<thread> m_serviceThread;
  };
}

LiveTimer::LiveTimer(time_duration interval)
    : m_interval(interval),
      m_deadLineTimer(TimerService::GetInstance().GetService()),
      m_deadlineSlot(bind(&LiveTimer::OnExpired, this, _1)) {}

LiveTimer::~LiveTimer() {}

void LiveTimer::Start() {
  lock_guard<mutex> lock(m_mutex);
  m_deadLineTimer.expires_from_now(m_interval);
  m_deadLineTimer.async_wait(m_deadlineSlot);
}

void LiveTimer::Cancel(Out<bool> expired) {
  unique_lock<mutex> lock(m_mutex);
  if(m_deadLineTimer.cancel() == 0) {
    *expired = true;
    return;
  }
  *expired = false;
  m_cancelledCondition.wait(lock);
}

void LiveTimer::SetExpiredSlot(const ExpiredSlot& slot) {
  lock_guard<mutex> lock(m_mutex);
  m_expiredSlot.reset(new ExpiredSlot(slot));
}

void LiveTimer::OnExpired(const system::error_code& error) {
  shared_ptr<ExpiredSlot> expiredSlot;
  {
    lock_guard<mutex> lock(m_mutex);
    if(error == asio::error::operation_aborted) {
      m_cancelledCondition.notify_one();
      return;
    }
    expiredSlot = m_expiredSlot;
  }
  (*expiredSlot)();
}
